导航菜单
首页 >  汇编汇编语言第5章BX和 loop 指令  > 【汇编语言】第五章 [BX]和loop指令

【汇编语言】第五章 [BX]和loop指令

 

目录前言5.1 [BX][bx]和内存单元的描述idata常量inc指令5.2 loop指令5.3 在Debug中跟踪用loop指令实现的循环程序g命令p命令5.4 Debug和汇编编译器masm对指令的不同处理5.5 loop和[bx]的联合应用5.6 段前缀5.7 一段安全的空间总结

 

前言

最近学了王爽教授写的《汇编语言》,整理一下学习笔记。

5.1 [BX][bx]和内存单元的描述

通过前面的学习我们知道。[···]表示内存单元,比如[2],它的偏移地址是2。[bx]同样表示一个内存单元,只不过它的偏移地址在bx中,段地址在ds中。

要完整的描述一个内存单元,需要两种信息: (1)内存单元的地址; (2)内存单元的长度;

以[bx]为例: mov ax, [bx]表示将一个内存单元的内容送入ax,这个内存单元的长度为2字节; mov al, [bx]表示将一个内存单元的内容送入al,这个内存单元的长度为1字节。

我们定义“()”来表示一个寄存器或一个内存单元中的内容。如: (ax)表示ax中的内容,(bl)表示bl中的内容。 要注意的是,“()”中的元素可以有三种类型: (1)寄存器名 (2)段寄存器名 (3)内存单元的物理地址(一个20位数据) 比如:(20000H)是正确的用法,但(2000: 0)是错误的用法。

idata常量

idata表示常量,比如: mov ax, [idata]就可以代表 mov ax, [1]、mov ax, [2]等; mov bx, idata就可以代表mov bx, 1、mov bx, 2等。

inc指令

inc,算数运算指令,表示加1,比如: mov bx, 1 inc bx 执行后 bx的值为2。 类似于高级语言中的“++运算符”

5.2 loop指令

loop,循环控制指令,Loop指令需要和cx寄存器配合使用, 用于循环操作, 类似于高级语言中的for, while等。循环次数存储在cx寄存器中,循环标号要自己定义,使用格式是:

mov cx, 循环次数 标号: (表明后面就是需要循环的循环体) 循环执行的程序代码 loop 标号

CPU在执行loop指令时,进行两步操作: (1) (cx)=(cx)-1; (2) 判断cx中的值,不为0则转至标号处执行程序,如果为0则向下执行。

比如:计算2^12,用add ax, ax指令的话要重复写11次,但是用loop指令就可以大大化简程序:

现在来分析一下上面的程序: (1)标号 汇编语言中,标号通常代表地址。上面程序中有一个标号s,它标识了一个地址,此地址处有一条指令:add ax,ax。 (2)loop s CPU执行loop s时,要进行两步操作: (cx)=(cx)-1; 判断(cx)中的值,不为0则转至标号s处所标识的地址,如果为0,则执行下一条指令(即mov ax, 4c00h)。

5.3 在Debug中跟踪用loop指令实现的循环程序

现在有一个问题:

计算 ffff:0006单元中的数乘以3,结果存储在dx中。

让我们来分析一下这个问题: (1)运算后的结果是否会超出dx的存储范围? ffff:0006中的数是一个字节型的数据,范围在0~255之间,则它和3相乘的结果不会大于65535,可以在dx中放下。 (2)用循环累加来实现乘法,用哪个寄存器进行累加? 我们可以将ffff:0006单元中的数赋值给ax,用dx进行累加。先设 (dx)=0 ,然后做3次 (dx)=(dx)+(ax) 。 (3)ffff:0006 单元是一个字节,ax十一个16位寄存器,数据长度不一样,如何赋值? 很显然,我们需要让ax的高八位为0,把ffff:0006赋值给ax的低八位,这样就可以解决这个问题了。

下面开始写代码:

注意:

程序中第一条指令,mov ax, 0ffffh。我们知道,大于9FFFh的十六进制数据如A000H、A001H等等,在书写的时候都是以字母开头的。而在汇编源程序中,数据不能以字母开头,所以要在前面加0。

下面我们对程序的执行过程进行跟踪。首先将它编辑成源文件,文件名为p3.asm;编译连接之后生成p3.exe;然后用Debug对p3.exe中的程序进行跟踪。

从图中我们可以看到,(ds)=075AH ,所以程序在076A: 0000处;CS: IP指向程序的第一条指令。

我们再用U命令看一下被Debug加载入内存的程序:

我们可以看到,从076A: 0000~076A: 001A是我们的程序,076A: 0014处是源程序中的指令loop s,只是此时标号s已经变成了一个地址0012h。在执行循环指令时,如果 (cx)-1 后不为 0,那么此时 “loop 0012” 就会把IP设置成0012h,从而使CS: IP指向076A: 0012处的add dx, ax,从而实现跳转。

g命令

由于循环程序从CS: 0012开始,在这前面的指令,我们不想再一步步地跟踪,想一次性执行完,然后从CS: 0012开始跟踪。可以用g命令来实现这样的操作:

Debug执行 “g 0012” 后,我们可以通过各个相关寄存器的值,看出执行结果。

p命令

但是现在我又嫌一次次循环跟踪太麻烦,我希望它一次性就可以将循环执行完。可以用p命令来达到我们的目的。以后当我们遇到loop指令时,就可以用p命令来自动重复执行循环中的指令,直到 (cx)=0 为止。

执行完循环,当前指令为CS: 0016处的 “mov ax,4c00”。当然,我们也可以直接用g命令,“g 0016”,来达到同样的目的。

5.4 Debug和汇编编译器masm对指令的不同处理

举个例子: 在Debug中:mov ax, [0] 表示将ds: 0处的数据送入ax中,这里[0]表示一个内存单元; 但是在汇编程序中,masm会将这句话解释为 mov ax, 0!!!

也就是说,对于[idata],Debug将它解释为偏移地址为idata的内存单元;而编译器将它解释为常数idata。 目前的解决方法是,可以将偏移地址送进一个寄存器bx,用[bx]的方式来访问内存单元就不会出错了,段地址默认在ds中。

5.5 loop和[bx]的联合应用

想一下这个问题:

计算 ffff: 0~ffff: b 单元中的数据和,结果存储在dx中。

先来分析一下这个问题: (1)运算后的结果是否会超过dx所能存储的范围? ffff: 0~ffff: b 内存单元中的数据是字节型,范围在 0 ~ 225 之间,12个这样的数据相加,结果不会大于65535,可以在 dx 中存放下。

(2)可不可以直接将 ffff: 0~ffff: b 中的数据累加到 dx 中,或者 dl 中? ffff: 0~ffff: b 中的数据是8位的,不能直接加到16位寄存器中。而且向 dl 中累加12个8位数据,很有可能造成进位丢失

(3)如何将 ffff: 0~ffff: b 中的8位数据,累加到16位寄存器 dx 中? 没办法,只能找一个16位寄存器当中介。将内存中的8位数据赋值到一个16位寄存器ax中,再将ax中的数据加到bx上。

话不多说,上代码:

5.6 段前缀

用于显示地指明内存单元的段地址的 “ds: ”、“cs: ”、“ss: ”、“es: ”,在汇编语言中称为段前缀。 比如 “mov ax, ds: [bx]”,然后来看一个问题:

将内存ffff: 0~ffff: b单元中的数据复制到0: 200 ~ 0: 20b单元中。

浅浅分析一下: (1)复制过程应用循环来实现,这样会方便很多,简单描述一下: 初始化: X=0 循环12次: 将ffff: X单元中的数据送入0020: X (需要用一个寄存器中转)、 X++; (2)在循环中,原始单元ffff: X和目标单元 0020: X的偏移地址X是变量。我们用bx来存放。 (3)将0: 200 ~ 0: 20b 用 0020: 0 ~ 0020: b描述,使原始单元和目标单元的偏移地址都从同一数值0开始。

话不多说过,上代码:

程序使用es访问目标空间 0020: 0 ~ 0020: b 的段地址,用ds存放原始空间ffff: 0 ~ ffff: b 的段地址。在访问内存单元的指令 “mov es: [bx], al” 中,用段前缀 “ es: ” 给出单元的段地址,这样就不必在循环中重复设置ds。

5.7 一段安全的空间

在8086模式中,随意向一段内存空间中写入内容是很危险的,因为这段内存空间中可能存放着重要的系统数据或代码。比如,在Windows 7的DOS方式中,在Debug里运行 “mov [0026], ax”这条指令时,就会出错:

将ax赋值为1,然后运行指令,结果发现:

产生这种结果的原因就是0: 0026处存放着重要的系统数据,而“mov [0026],ax ” 将其改写了。 可见,我们要向内存空间写入数据的话,要使用操作系统给我们分配的空间,而不应该直接用地址任意指定内存单元,向里面写入。

DOS方式下,一般情况,0:200 ~ 0:2ff 空间中没有系统或其他程序的数据或代码,我们可以使用这段空间来向内存写入内容。

总结

以上为本人学习汇编语言时的摘录总结,主要内容来源于汇编语言(第四版) 王爽 著,大家若是感兴趣可以看看原书,很值得推荐,以上内容如果有什么错误的话,还请大家指正!

相关推荐: